<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
	<title>Gox Editor</title>
    <style>
    
    plaintext {
      padding:0;
      flow:vertical;
      behavior:plaintext;
      background:#333; border:1px solid #333;
      color:white;
	  overflow:scroll-indicator;
      font-rendering-mode:snap-pixel;
      size:*; 
      tab-size: 4;
    }
    plaintext > text {
      font-family:monospace;
      white-space: pre-wrap;
      background:white;
      color:black;
      margin-left: 3em;
      padding-left: 4dip;
      cursor:text;
      display:list-item;
      list-style-type: index;
      list-marker-color:#aaa;
    }
    plaintext > text:last-child {
      padding-bottom:*;
    }    
    
    plaintext > text:nth-child(10n) {
      list-marker-color:#fff;
    }
    
    
    </style>


	<script type="text/tiscript">
		function colorize() 
		{
			const apply = Selection.applyMark; // shortcut
			const isEditor = this.tag == "plaintext";
			
			// forward declarations:
			var doStyle;
			var doScript;

			// markup colorizer  
			function doMarkup(tz) 
			{
					var bnTagStart = null;
					var tagScript = false;
					var tagScriptType = false;
					var tagStyle = false;
					var textElement;
				
				while(var tt = tz.token()) {
				if( isEditor && tz.element != textElement )       
				{
					textElement = tz.element;
					textElement.attributes["type"] = "markup";
				}
				//stdout.println(tt,tz.attr,tz.value);
				switch(tt) {
					case #TAG-START: {    
						bnTagStart = tz.tokenStart; 
						const tag = tz.tag;
						tagScript = tag == "script";
						tagStyle  = tag == "style";
					} break;
					case #TAG-HEAD-END: {
						apply(bnTagStart,tz.tokenEnd,"tag"); 
						if( tagScript ) { tz.push(#source,"</sc"+"ript>"); doScript(tz, tagScriptType, true); }
						else if( tagStyle ) { tz.push(#source,"</style>"); doStyle(tz, true); }
					} break;
					case #TAG-END:      apply(tz.tokenStart,tz.tokenEnd,"tag"); break;  
					case #TAG-ATTR:     if( tagScript && tz.attr == "type") tagScriptType = tz.value; 
										if( tz.attr == "id" ) apply(tz.tokenStart,tz.tokenEnd,"tag-id"); 
										break;
				}
				}
			}
			
			// script colorizer
			doScript = function(tz, typ, embedded = false) 
			{
				const KEYWORDS = 
				{
				"type"    :true, "function":true, "var"       :true,"if"       :true,
				"else"    :true, "while"   :true, "return"    :true,"for"      :true,
				"break"   :true, "continue":true, "do"        :true,"switch"   :true,
				"case"    :true, "default" :true, "null"      :true,"super"    :true,
				"new"     :true, "try"     :true, "catch"     :true,"finally"  :true,
				"throw"   :true, "typeof"  :true, "instanceof":true,"in"       :true,
				"property":true, "const"   :true, "get"       :true,"set"      :true,
				"include" :true, "like"    :true, "class"     :true,"namespace":true,
				"this"    :true, "assert"  :true, "delete"    :true,"otherwise":true,
				"with"    :true, "__FILE__":true, "__LINE__"  :true,"__TRACE__":true,
				"debug"   :true, "await"   :true 
				};
				
				const LITERALS = { "true": true, "false": true, "null": true, "undefined": true };
				
				var firstElement;
				var lastElement;
			
				while:loop(var tt = tz.token()) {
				var el = tz.element;
				if( !firstElement ) firstElement = el;
				lastElement = el;
				switch(tt) 
				{
					case #NUMBER:       apply(tz.tokenStart,tz.tokenEnd,"number"); break; 
					case #NUMBER-UNIT:  apply(tz.tokenStart,tz.tokenEnd,"number-unit"); break; 
					case #STRING:       apply(tz.tokenStart,tz.tokenEnd,"string"); break;
					case #NAME:         
					{
					var val = tz.value;
					if( val[0] == '#' )
						apply(tz.tokenStart,tz.tokenEnd, "symbol"); 
					else if(KEYWORDS[val]) 
						apply(tz.tokenStart,tz.tokenEnd, "keyword"); 
					else if(LITERALS[val]) 
						apply(tz.tokenStart,tz.tokenEnd, "literal"); 
					break;
					}
					case #COMMENT:      apply(tz.tokenStart,tz.tokenEnd,"comment"); break;
					case #END-OF-ISLAND:  
					// got </scr ipt>
					tz.pop(); //pop tokenizer layer
					break loop;
				}
				}
				if(isEditor && embedded) {
				for( var el = firstElement; el; el = el.next ) {
					el.attributes["type"] = "script";
					if( el == lastElement )
					break;
				}
				}
			};
			
			doStyle = function(tz, embedded = false) 
			{
				const KEYWORDS = 
				{
				"rgb":true, "rgba":true, "url":true, 
				"@import":true, "@media":true, "@set":true, "@const":true
				};
				
				const LITERALS = { "inherit": true };
				
				var firstElement;
				var lastElement;
				
				while:loop(var tt = tz.token()) {
				var el = tz.element;
				if( !firstElement ) firstElement = el;
				lastElement = el;
				switch(tt) 
				{
					case #NUMBER:       apply(tz.tokenStart,tz.tokenEnd,"number"); break; 
					case #NUMBER-UNIT:  apply(tz.tokenStart,tz.tokenEnd,"number-unit"); break; 
					case #STRING:       apply(tz.tokenStart,tz.tokenEnd,"string"); break;
					case #NAME:         
					{
					var val = tz.value;
					if( val[0] == '#' )
						apply(tz.tokenStart,tz.tokenEnd, "symbol"); 
					else if(KEYWORDS[val]) 
						apply(tz.tokenStart,tz.tokenEnd, "keyword"); 
					else if(LITERALS[val]) 
						apply(tz.tokenStart,tz.tokenEnd, "literal"); 
					break;
					}
					case #COMMENT:      apply(tz.tokenStart,tz.tokenEnd,"comment"); break;
					case #END-OF-ISLAND:  
					// got </sc ript>
					tz.pop(); //pop tokenizer layer
					break loop;
				}
				}
				if(isEditor && embedded) {
				for( var el = firstElement; el; el = el.next ) {
					el.attributes["type"] = "style";
					if( el == lastElement )
					break;
				}
				}
			};
			
			var me = this;
			
			function doIt() { 
			
				var typ = me.attributes["type"];

				var syntaxKind = typ like "*html" || typ like "*xml" ? #markup : #source;
				var syntax = typ like "*css"? #style : #script;
				
				var tz = new Tokenizer( me, syntaxKind );
			
				if( syntaxKind == #markup )
				doMarkup(tz);
				else if( syntax == #style )
				doStyle(tz);
				else 
				doScript(tz,typ);
			}
			
			doIt();
			
			// redefine value property
			this[#value] = property(v) {
				get { return this.state.value; }
				set { this.state.value = v; doIt(); }
			};
			
			this.load = function(text,sourceType) 
			{
				this.attributes["type"] = sourceType;
				if( !isEditor )
				text = text.replace(/\r\n/g,"\n"); 
				this.state.value = text; 
				doIt();
			};
			
			this.sourceType = property(v) {
				get { return this.attributes["type"]; }
				set { this.attributes["type"] = v; doIt(); }
			};
			if (isEditor)
					this.on("change", function() {
						this.timer(40ms,doIt);
					});
			

		}
	</script>
	<style>

		@set colorizer < std-plaintext 
		{
			:root { aspect: colorize; }
			
			text { white-space:pre;  display:list-item; list-style-type: index; list-marker-color:#aaa; }
			/*markup*/  
			text::mark(tag) { color: olive; } /*background-color: #f0f0fa;*/
			text::mark(tag-id) { color: red; } /*background-color: #f0f0fa;*/

			/*source*/  
			text::mark(number) { color: brown; }
			text::mark(number-unit) { color: brown; }
			text::mark(string) { color: teal; }
			text::mark(keyword) { color: blue; }
			text::mark(symbol) { color: brown; }
			text::mark(literal) { color: brown; }
			text::mark(comment) { color: green; }
			
			text[type=script] {  background-color: #FFFAF0; }
			text[type=markup] {  background-color: #FFF;  }
			text[type=style]  {  background-color: #FAFFF0; }
		}

		plaintext[type] {
			style-set: colorizer;
		}

		@set element-colorizer 
		{
			:root { 
				aspect: colorize; 
				background-color: #fafaff;
					padding:4dip;
					border:1dip dashed #bbb;
				}
			
			/*markup*/  
			:root::mark(tag) { color: olive; } 
			:root::mark(tag-id) { color: red; }

			/*source*/  
			:root::mark(number) { color: brown; }
			:root::mark(number-unit) { color: brown; }
			:root::mark(string) { color: teal; }
			:root::mark(keyword) { color: blue; }
			:root::mark(symbol) { color: brown; }
			:root::mark(literal) { color: brown; }
			:root::mark(comment) { color: green; }
			}

			pre[type] {
			style-set: element-colorizer;
		}

	</style>
	<script type="text/tiscript">
		// if (view.connectToInspector) {
		// 	view.connectToInspector(rootElement, inspectorIpAddress);
		// }

		//stdout.println("__FOLDER__:", __FOLDER__);
		//stdout.println("__FILE__:", __FILE__);

		function isErrStr(strA) {
			if (strA.substr(0, 6) == "TXERROR:") {
				return true;
			}

			return false;
		}

		function getErrStr(strA) {
			if (strA.substr(0, 6) == "TXERROR:") {
				return strA.substr(6);
			}

			return strA;
		}

		function getConfirm(titelA, msgA) {
			var result = view.msgbox { 
				type:#question,
				title: titelA,
				content: msgA, 
				//buttons:[#yes,#no]
				buttons: [
					{id:#yes,text:"Ok",role:"default-button"},
					{id:#cancel,text:"Cancel",role:"cancel-button"}]                               
				};

			return result;
		}

		function showInfo(titelA, msgA) {
			var result = view.msgbox { 
				type:#information,
				title: titelA,
				content: msgA, 
				//buttons:[#yes,#no]
				buttons: [
					{id:#cancel,text:"Close",role:"cancel-button"}]                               
				};

			return result;
		}

		function showError(titelA, msgA) {
			var result = view.msgbox { 
				type:#alert,
				title: titelA,
				content: msgA, 
				//buttons:[#yes,#no]
				buttons: [
					{id:#cancel,text:"Close",role:"cancel-button"}]                               
				};

			return result;
		}

		function getScreenWH() {
//			view.prints(String.printf("screenBoxO: %v, %v", 1, 2));
			var (w, h) = view.screenBox(#frame, #dimension)
//			view.prints(String.printf("screenBox: %v, %v", w, h));

			view.move((w-800)/2, (h-600)/2, 800, 600);

			return String.printf("%v|%v", w, h);
		}

		var editFileNameG = "";
		var editFileCleanFlagG = "";

		function updateFileName() {
			$(#fileNameLabelID).html = (editFileNameG + editFileCleanFlagG);
		}

		function selectFileJS() {
			//var fn = view.selectFile(#open, "Gotx Files (*.gt,*.go)|*.gt;*.go|All Files (*.*)|*.*" , "gotx" );
			var fn = view.selectFile(#open);
			view.prints(String.printf("fn: %v", fn));
			//view.prints(String.printf("screenBox: %v", view.screenBox(#frame, #dimension)));

			if (fn == undefined) {
				return;
			}

			var fileNameT = URL.toPath(fn);

			var rs = view.loadStringFromFile(fileNameT);

			if (isErrStr(rs)) {
				showError("Error", String.printf("Failed to load file content: %v", getErrStr(rs)));
				return;
			}

			$(plaintext).attributes["type"] = "text/script";

			$(plaintext).value = rs;

			editFileNameG = fileNameT;

			editFileCleanFlagG = "";

			updateFileName();

			// return fn;
		}

		function editFileLoadClick() {
			if (editFileCleanFlagG != "") {
			
				var rs = getConfirm("Please confirm", "File modified, load another file anyway?");

				if (rs != #yes) {
					return;
				}

			}

			selectFileJS();
		}

		function editFileSaveAsClick() {
			var fn = view.selectFile(#save);
			view.prints(String.printf("fn: %v", fn));

			if (fn == undefined) {
				return;
			}

			var fileNameT = URL.toPath(fn);

			var textT = $(plaintext).value;

			var rs = view.saveStringToFile(textT, fileNameT);

			if (isErrStr(rs)) {
				showError("Error", String.printf("Failed to save file content: %v", getErrStr(rs)));
				return;
			}

			editFileNameG = fileNameT;
			editFileCleanFlagG = "";
			updateFileName();

			showInfo("Info", "Saved.");

		}

		function editFileSaveClick() {
			if (editFileNameG == "") {
				editFileSaveAsClick();

				return;
			}

			var textT = $(plaintext).value;

			var rs = view.saveStringToFile(textT, editFileNameG);

			if (isErrStr(rs)) {
				showError("Error", String.printf("Failed to save file content: %v", getErrStr(rs)));
				return;
			}

			editFileCleanFlagG = "";
			updateFileName();

			showInfo("Info", "Saved.");
		}

		function editRunClick() {
			view.close();
			// view.exit();
		}

		function getInput(msgA) {
			var res = view.dialog({ 
				html: `
				<html>
				<body>
				  <center>
					  <div style="margin-top: 10px; margin-bottom: 10px;">
						  <span>`+msgA+`</span>
					  </div>
					  <div style="margin-top: 10px; margin-bottom: 10px;">
						  <input id="mainTextID" type="text" />
					  </div>
					  <div style="margin-top: 10px; margin-bottom: 10px;">
						  <input id="submitButtonID" type="button" value="Ok" />
						  <input id="cancelButtonID" type="button" value="Cancel" />
					  </div>
				  </center>
				  <script type="text/tiscript">
					  $(#submitButtonID).onClick = function() {
						  view.close($(#mainTextID).value);
					  };
  
					  $(#cancelButtonID).onClick = function() {
						  view.close();
					  };
				  </scr`+`ipt>
				</body>
				</html>
				`
			  });
  
			  return res;
		  }

		event click $(#btnEncrypt)
		{
		  	var res = getInput("Secure Code");

			if (res == undefined) {
				return;
			}

			var sourceT = $(plaintext).value;

			var encStrT = view.encryptText(sourceT, res);
		
			if (isErrStr(encStrT)) {
				showError("Error", String.printf("failed to encrypt content: %v",getErrStr(encStrT)));
				return;
			}
		
			$(plaintext).value = "\/\/TXDEF#" + encStrT;
			editFileCleanFlagG = "*";
			updateFileName();
		}
	
		event click $(#btnDecrypt)
		{
		  	var res = getInput("Secure Code");

			if (res == undefined) {
				return;
			}

			var sourceT = $(plaintext).value;

			var encStrT = view.decryptText(sourceT, res);
		
			if (isErrStr(encStrT)) {
				showError("Error", String.printf("failed to decrypt content: %v",getErrStr(encStrT)));
				return;
			}
		
			$(plaintext).value = encStrT;
			editFileCleanFlagG = "*";
			updateFileName();
		}
	
		event click $(#btnRun)
		{
			var res = getInput("Arguments to pass to script")

			if (res == undefined) {
				return;
			}

			var rs = view.runScript($(plaintext).value, res);

			showInfo("Result", rs)
		 
		  	// view.prints(String.printf("result = %v", rs));
		}
	

		function editCloseClick() {
			view.close();
			// view.exit();
		}

		function editFile(fileNameA) {
			var fcT string;

			//view.prints("fileNameA: "+fileNameA);

			if (fileNameA == "") {
				editFileNameG = "";

				fcT = "";

				editFileCleanFlagG = "*";
			} else {
				editFileNameG = fileNameA;

				fcT = view.loadStringFromFile(fileNameA);

//		if tk.IsErrorString(fcT) {
//			tk.Pl("failed to load file %v: %v", editFileNameG, tk.GetErrorString(fcT))
//			return

//		}

				editFileCleanFlagG = "";
			}

			//view.prints(fcT);

			$(plaintext).attributes["type"] = "text/script";

			$(plaintext).value = fcT;

			updateFileName();

		}

		function self.ready() {

			//$(plaintext).value = "<html>\n<body>\n<span>abc</span>\n</body></html>";

			$(#btnLoad).onClick = editFileLoadClick;
			$(#btnSave).onClick = editFileSaveClick;
			$(#btnSaveAs).onClick = editFileSaveAsClick;
			// $(#btnEncrypt).onClick = editFEncryptClick;
			// $(#btnDecrypt).onClick = editDecryptClick;
			// $(#btnRun).onClick = editRunClick;
			$(#btnClose).onClick = editCloseClick;

			$(plaintext#source).onControlEvent = function(evt) {
				switch (evt.type) {
					case Event.EDIT_VALUE_CHANGED:      
						editFileCleanFlagG = "*";
						updateFileName();
						return true;
				}
			};

		}
	</script>

</head>
<body>
	<div style="margin-top: 10px; margin-bottom: 10px;"><span id="fileNameLabelID"></span></div>
	<div style="margin-top: 10px; margin-bottom: 10px;">
		<button id="btn1" style="display: none">Load...</button>
		<button id="btnLoad">Load</button>
		<button id="btnSave">Save</button>
		<button id="btnSaveAs">SaveAs</button>
		<button id="btnEncrypt">Encrypt</button>
		<button id="btnDecrypt">Decrypt</button>
		<button id="btnRun">Run</button>
		<button id="btnClose">Close</button>
	</div>
	<plaintext#source type="text/html" style="font-size: 1.2em;"></plaintext>

</body>
</html>